﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using MiniShellFramework;
using MiniShellFramework.ComTypes;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Diagnostics.Contracts;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using Menu = MiniShellFramework.Menu;
using PerformanceDude.MSBuildShellExtension.Common;
using System.Windows.Interop;

/*
 * Copyright (c) 2007-2012, Thomas Ardal (thomasardalatgmail.com), Lex Li (supportatlextm.com), Rami Abughazaleh (rami.abughazalehatgmail.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of Thomas Ardal nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
namespace PerformanceDude.MSBuildShellExtension
{
    [ComVisible(true)]                              // Make this .NET class a COM object (ComVisible is false on assembly level).
    [Guid("B6D077CB-F823-4F5B-9E44-F3719175B04C")]  // Explicitly assign a GUID: easier to reference and to debug.
    [ClassInterface(ClassInterfaceType.None)]       // Only the functions from the COM interfaces should be accessible.
    public sealed class ContextMenu : ContextMenuBase, IDisposable
    {
        private static ConfigurationSettings Settings;

        private readonly Bitmap menuBitmap;
        private readonly Bitmap helpBitmap;
        private readonly Bitmap aboutBitmap;
        private readonly Bitmap profileBitmap;
        private readonly Bitmap administratorBitmap;
        private readonly Bitmap settingsBitmap;
        private static Menu contextMenu;
        private bool IsWindowsVistaOrHigher = false;

        public ContextMenu()
        {
            try
            {
                //For debugging purposes
                //System.Diagnostics.Debugger.Launch();

                AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(Common.ExceptionHandler.CurrentDomain_UnhandledException);
                AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(Common.AssemblyResolver.CurrentDomain_AssemblyResolve);

                Settings = new ConfigurationSettings(true);

                OperatingSystem OS = Environment.OSVersion;
                this.IsWindowsVistaOrHigher = (OS.Platform == PlatformID.Win32NT) && (OS.Version.Major >= 6);

                menuBitmap = new Bitmap(typeof(ContextMenu), "menuicon.bmp");
                menuBitmap.MakeTransparent(Color.White);

                Icon settingsIcon = new Icon(new Icon(typeof(ContextMenu), "settings.ico"), new Size(16, 16));
                settingsBitmap = settingsIcon.ToBitmap();
                settingsBitmap.MakeTransparent(Color.White);

                Icon helpIcon = new Icon(new Icon(typeof(ContextMenu), "Help.ico"), new Size(16, 16));
                helpBitmap = helpIcon.ToBitmap();
                helpBitmap.MakeTransparent(Color.White);

                Icon aboutIcon = new Icon(new Icon(typeof(ContextMenu), "About.ico"), new Size(16, 16));
                aboutBitmap = aboutIcon.ToBitmap();
                aboutBitmap.MakeTransparent(Color.White);

                Icon profileIcon = new Icon(new Icon(typeof(ContextMenu), "timer.ico"), new Size(16, 16));
                profileBitmap = profileIcon.ToBitmap();
                profileBitmap.MakeTransparent(Color.White);

                if (this.IsWindowsVistaOrHigher)
                {
                    Icon administratorIcon = new Icon(new Icon(typeof(ContextMenu), "shield.ico"), new Size(16, 16));
                    administratorBitmap = administratorIcon.ToBitmap();
                    administratorBitmap.MakeTransparent(Color.White);
                }

                foreach (string fileExtension in MSBuildRootKey.FileExtensions)
                {
                    RegisterExtension(fileExtension);
                }
            }
            catch (Exception ex)
            {
                ExceptionHandler.Log(ex);
                throw;
            }
        }

        [ComRegisterFunction]
        public static void ComRegisterFunction(Type type)
        {
            Contract.Requires(type != null);

            MSBuildRootKey.Register();

            bool fileAssociationExists = false;

            foreach (string fileExtension in MSBuildRootKey.FileExtensions)
            {
                string existingProgId;
                if (RootKey.TryGetProgIdForFileExension(fileExtension, out existingProgId))
                {
                    fileAssociationExists = true;
                    ComRegister(type, MSBuildRootKey.Description, existingProgId);
                }
            }

            if (!fileAssociationExists)
            {
                ComRegister(type, MSBuildRootKey.Description, MSBuildRootKey.ProgId);
            }
        }

        [ComUnregisterFunction]
        public static void ComUnregisterFunction(Type type)
        {
            Contract.Requires(type != null);

            //do not unregister file extensions or progid because it may break existing file associations
            //MSBuildRootKey.Unregister();
            //ComUnregister(type, MSBuildRootKey.Description, MSBuildRootKey.ProgId);
        }

        protected override void QueryContextMenuCore(Menu menu, IList<string> filenames)
        {
            try
            {
                if (filenames.Count != 1 || ContainsUnknownExtension(filenames))
                    return;

                //TODO: Low: support multiple file selections

                string msbuildFileName = filenames[0];

                contextMenu = menu.AddSubMenu("MSBuild", new BitmapCustomMenuHandler("MSBuild", menuBitmap));

                MSBuildFile msBuildFile = new MSBuildFile(msbuildFileName);
                foreach (MSBuildFileTarget target in msBuildFile.Targets)
                {
                    //we have to declare a local instance variable and use it in the delegate body so that the value is used at the time the delegate is created
                    MSBuildFileTarget _target = target;

                    contextMenu.AddItem(target.FullTargetName, "Execute Target \"" + target.FullTargetName + "\" on the file with MSBuild",
                        (ref InvokeCommandInfo invokeCommandInfo, IList<string> fileNames) =>
                        {
                            RunMsBuildAsync(new MSBuildOptions()
                            {
                                FileName = msbuildFileName,
                                DotNetVersion = msBuildFile.VersionString,
                                TargetName = _target.TargetName,
                                ConfigurationName = _target.ConfigurationName,
                                PlatformName = _target.PlatformName
                            });
                        });
                }

                Menu administratorSubMenu = null;
                if (this.IsWindowsVistaOrHigher)
                {
                    administratorSubMenu = contextMenu.AddSubMenu("Run as administrator", new BitmapCustomMenuHandler("Run as administrator", administratorBitmap));
                }

                Menu profileSubMenu = contextMenu.AddSubMenu("Profile a target using MSBuild Profiler", new BitmapCustomMenuHandler("Profile", profileBitmap));

                foreach (MSBuildFileTarget target in msBuildFile.Targets)
                {
                    //we have to declare a local instance variable and use it in the delegate body so that the value is used at the time the delegate is created
                    MSBuildFileTarget _target = target;

                    if (administratorSubMenu != null)
                    {
                        administratorSubMenu.AddItem(target.FullTargetName, "Execute Target \"" + target.FullTargetName + "\" on the file with MSBuild",
                            (ref InvokeCommandInfo invokeCommandInfo, IList<string> fileNames) =>
                            {
                                RunMsBuildAsync(new MSBuildOptions()
                                {
                                    FileName = msbuildFileName,
                                    DotNetVersion = msBuildFile.VersionString,
                                    TargetName = _target.TargetName,
                                    ConfigurationName = _target.ConfigurationName,
                                    PlatformName = _target.PlatformName,
                                    RunAsAdministrator = true
                                });
                            });
                    }

                    profileSubMenu.AddItem(target.FullTargetName, "Profile Target \"" + target.FullTargetName + "\" on the file with MSBuild Profiler",
                        (ref InvokeCommandInfo invokeCommandInfo, IList<string> fileNames) =>
                        {
                            RunMsBuildAsync(new MSBuildOptions()
                            {
                                FileName = msbuildFileName,
                                DotNetVersion = msBuildFile.VersionString,
                                TargetName = _target.TargetName,
                                ConfigurationName = _target.ConfigurationName,
                                PlatformName = _target.PlatformName,
                                Profile = true
                            });
                        });
                }

                contextMenu.AddItem(new BitmapCustomMenuHandler("&Settings", settingsBitmap), "Configure settings for MSBuild Shell Extension", OnSettings);
                contextMenu.AddItem(new BitmapCustomMenuHandler("&Help", helpBitmap), "View the help documentation for MSBuild Shell Extension", OnHelp);
                contextMenu.AddItem(new BitmapCustomMenuHandler("&About MSBuild Shell Extension", aboutBitmap), "Show information about MSBuild Shell Extension", OnAboutMSBuild);
            }
            catch (Exception ex)
            {
                ExceptionHandler.Log(ex);
                throw;
            }
        }

        private static void RunMsBuildAsync(MSBuildOptions options)
        {
            options.LoggingVerbosity = Settings.LoggingVerbosity;
            options.MaxCpuCount = Settings.MaxCpuCount;

            MSBuildRunner msBuildRunner = new MSBuildRunner(options);
            msBuildRunner.RunAsync();
        }

        private static void OnSettings(ref InvokeCommandInfo invokeCommandInfo, IList<string> fileNames)
        {
            try
            {
                //explicitly load assemblies used in xaml only because of an inherit problem in WPF
                //http://compositewpf.codeplex.com/discussions/353441

                string wpfToolkitExtendedDllFilePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "WPFToolkit.Extended.dll");
                Assembly.LoadFrom(wpfToolkitExtendedDllFilePath);

                SettingsView settingsView = new SettingsView(Settings);
                settingsView.ShowDialog();
            }
            catch (Exception ex)
            {
                ExceptionHandler.Log(ex);
                throw;
            }
        }

        private static void OnAboutMSBuild(ref InvokeCommandInfo invokeCommandInfo, IList<string> fileNames)
        {
            AboutDialog aboutDialog = new AboutDialog();
            aboutDialog.ShowDialog();
        }

        private static void OnHelp(ref InvokeCommandInfo invokeCommandInfo, IList<string> fileNames)
        {
            string dllFilePath = Assembly.GetExecutingAssembly().Location;
            string appPath = System.IO.Path.GetDirectoryName(dllFilePath);
            Process.Start(Path.Combine(appPath, "MSBuildShellEx.chm"));
        }

        public void Dispose()
        {
            Debug.WriteLine("{0}.Dispose", this);
            menuBitmap.Dispose();
        }
    }
}
